// 材质
// Created by denglibin on 2021/4/7.
//

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "shader.h"
#include "window.h"
#include "gl_object.h"
#include "cglm.h"
#include "camera.h"
// settings
static const  int width = 800;
static const  int height = 600;
//顶点坐标
static float vertices[] = {
        //位置                    法向量
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,

        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,

        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,

        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,

        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
};

static Camera *camera;
static vec3 lightPos = {0.5f, 1.0f, 3.0f}; //光源位置
static float angle =0;
static vec3 objectColor = {1.0f, 0.5f, 0.31f}; //物体颜色
static vec3 lightColor = { 1.0f, 1.0f, 1.0f}; //灯颜色
//鼠标回调,改变摄像机的方向
static void mouse_callback(GLFWwindow* window, double x, double y);
//滚轮回调,当滚动鼠标滚轮的时候，y_offset值代表我们竖直滚动的大小
//当scroll_callback函数被调用后，我们改变全局变量fov变量的内容。因为45.0f是默认的视野值，我们将会把缩放级别(Zoom Level)限制在1.0f到45.0f。
static void scroll_callback(GLFWwindow* window, double x_offset, double y_offset);

/**
 * 输入事件监听，改变摄像机的位置
 */
static void processInput1(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS){
        glfwSetWindowShouldClose(window, 1);
    }
    //按键事件交给相机处理
    pressKeyMove(camera, window);

}
/**
 * 设置链接顶点属性
 */
static void setVertexAttr()
{
    //顶点位置
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    //法向量
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*) (3 * sizeof(float)));
    glEnableVertexAttribArray(1);
}

/**
 * 设置物体uniform颜色变量
 * @param shaderProgram
 */
static void setObjectColorUniform(GLuint shaderProgram){


    //材质变量
    vec3  material_ambient = {1.0f, 0.5f, 0.31f}; //在环境光照下这个物体反射得是什么颜色，通常这是和物体颜色相同的颜色
    vec3  material_diffuse = {1.0f, 0.5f, 0.31f}; //在漫反射光照下物体的颜色。（和环境光照一样）漫反射颜色也要设置为我们需要的物体颜色
    vec3  material_specular = {0.5f, 0.5f, 0.5f}; //镜面光照对物体的颜色影响
    float  material_shininess= 32.0f; //影响镜面高光的散射/半径

    //光源变量
    vec3  light_ambient = {0.2f, 0.2f, 0.2f}; //环境光照通常会设置为一个比较低的强度，因为我们不希望环境光颜色太过显眼
    vec3  light_diffuse = {0.5f, 0.5f, 0.5f}; //漫反射分量通常设置为光所具有的颜色，通常是一个比较明亮的白色; 将光照调暗了一些以搭配场景
    vec3  light_specular = {1.0f, 1.0f, 1.0f}; //镜面光分量通常会保持为vec3(1.0)，以最大强度发光

    //改变光源颜色
    lightColor[0]= sinf((float)glfwGetTime() * 2.0f);
    lightColor[1]= sinf((float)glfwGetTime() * 0.7f);
    lightColor[2]= sinf((float)glfwGetTime() * 1.3f);



    setUniformVec3(shaderProgram, "lightPos", lightPos);
    setUniformVec3(shaderProgram, "lightColor", lightColor);
    setUniformVec3(shaderProgram, "objectColor", objectColor);
    setUniformVec3(shaderProgram, "viewPos", camera->cameraPos);

    //设置材质变量
    setUniformVec3(shaderProgram, "material.ambient",  material_ambient);
    setUniformVec3(shaderProgram, "material.diffuse",  material_diffuse);
    setUniformVec3(shaderProgram, "material.specular", material_specular);
    setUniformFloat(shaderProgram, "material.shininess", material_shininess);

    //设置光源变量
    setUniformVec3(shaderProgram, "light.ambient",  light_ambient);
    setUniformVec3(shaderProgram, "light.diffuse",  light_diffuse);
    setUniformVec3(shaderProgram, "light.specular", light_specular);

}

/**
 * 物体模型矩阵
 * @param shaderProgram
 */
static void objectModelMat(GLuint shaderProgram){
    mat4 model = GLM_MAT4_IDENTITY_INIT; //定义一个变换矩阵 单位矩阵
    unsigned int transformLoc = glGetUniformLocation(shaderProgram, "objectModel");
    glUniformMatrix4fv(transformLoc, 1, GL_FALSE, (const GLfloat *) model);

}
/**
 * 光源模型矩阵
 * @param shaderProgram
 */
static void lightModelMat(GLuint shaderProgram){

    //动态改变光源位置
    float radius = 2.0f; //旋转半径
    float x = sinf(angle) * radius; //位置 x坐标
    float z = cosf(angle) * radius; //位置 z坐标
    angle += 0.05f;
 /*   lightPos[0] = x;
    lightPos[2] = z;*/

    mat4 model = GLM_MAT4_IDENTITY_INIT; //定义一个变换矩阵 单位矩阵

    vec3 lightScale = {0.2f, 0.2f, 0.2f};

    glm_translate(model, lightPos); //平移
    glm_scale(model, lightScale); //缩放


    unsigned int transformLoc = glGetUniformLocation(shaderProgram, "lightModel");
    setUniformVec3(shaderProgram, "lightColor", lightColor);
    glUniformMatrix4fv(transformLoc, 1, GL_FALSE, (const GLfloat *) model);

}


/**
 * 观察矩阵
 * 观察空间经常被人们称之OpenGL的摄像机(Camera)（所以有时也称为摄像机空间(Camera Space)或视觉空间(Eye Space)）。观察空间是将世界空间坐标转化为用户视野前方的坐标而产生的结果。因此观察空间就是从摄像机的视角所观察到的空间。
 * 而这通常是由一系列的位移和旋转的组合来完成，平移/旋转场景从而使得特定的对象被变换到摄像机的前方。这些组合在一起的变换通常存储在一个观察矩阵(View Matrix)里，它被用来将世界坐标变换到观察空间
 * @param shaderProgram
 */
static void viewMat(GLuint shaderProgram){

    mat4 view = GLM_MAT4_IDENTITY_INIT; //定义一个变换矩阵 单位矩阵
    vec3 target = GLM_VEC3_ZERO_INIT;
    //摄像机位置 + 摄像机方向 = 目标位置
    glm_vec3_add(camera->cameraPos, camera->cameraFront, target);
    //LookAt函数需要一个位置、目标和上向量
    glm_lookat(camera->cameraPos, target, camera->cameraUp, view);
    unsigned int transformLoc = glGetUniformLocation(shaderProgram, "view");
    glUniformMatrix4fv(transformLoc, 1, GL_FALSE, (const GLfloat *) view);

}

/**
 * 投影矩阵 使用透视投影
 * @param shaderProgram
 */
static void projectionMat(GLuint shaderProgram){
    mat4 projection = GLM_MAT4_IDENTITY_INIT; //定义一个变换矩阵 单位矩阵
    glm_perspective(camera->fovy, (float )width/(float )height, 0.1f, 100.0f, projection);
    unsigned int transformLoc = glGetUniformLocation(shaderProgram, "projection");
    glUniformMatrix4fv(transformLoc, 1, GL_FALSE, (const GLfloat *) projection);

}




int main2_3(void)
{
    //初始化，创建窗口
    GLFWwindow *window = initWindow(width, height, "LearnOpenGL");
    camera = initCamera();//创建相机

    glEnable(GL_LINE_SMOOTH); //启用
    glEnable(GL_POINT_SMOOTH); //启用
    //调用这个函数之后，无论我们怎么去移动鼠标，光标都不会显示了，它也不会离开窗口。对于FPS摄像机系统来说非常完美。
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    //注册鼠标回调函数
    glfwSetCursorPosCallback(window, mouse_callback);
    //注册鼠标滚轮的回调函数
    glfwSetScrollCallback(window, scroll_callback);
    /**
     * OpenGL存储它的所有深度信息于一个Z缓冲(Z-buffer)中，也被称为深度缓冲(Depth Buffer)。GLFW会自动为你生成这样一个缓冲（就像它也有一个颜色缓冲来存储输出图像的颜色）。深度值存储在每个片段里面（作为片段的z值），当片段想要输出它的颜色时，OpenGL会将它的深度值和z缓冲进行比较，如果当前的片段在其它片段之后，它将会被丢弃，否则将会覆盖。这个过程称为深度测试(Depth Testing)，它是由OpenGL自动完成的
     **/
    //开启深度测试
    glEnable(GL_DEPTH_TEST);
    //创建着色器程序
    //编译着色器源码，创建着色器程序
    GLuint objectShaderProgram = compileShaderSource("D:\\CLionProjects\\opengl-mingw\\shader2\\shader2_object.vs",
                                                     "D:\\CLionProjects\\opengl-mingw\\shader2\\shader3_object.fs");

    GLuint lightShaderProgram = compileShaderSource("D:\\CLionProjects\\opengl-mingw\\shader2\\shader2_light.vs",
                                                    "D:\\CLionProjects\\opengl-mingw\\shader2\\shader2_light.fs");


    GLuint VBO = createVBO(vertices, sizeof (vertices) / sizeof (float ));

    //创建物体的VAO
    GLuint objectVAO = createVAO();
    setVertexAttr();

    //创建灯的VAO （使用同一个VBO）
    GLuint lightVAO = createVAO();
    setVertexAttr();



    //一般当你打算绘制多个物体时，你首先要生成/配置所有的VAO（和必须的VBO及属性指针)，然后储存它们供后面使用。当我们打算绘制物体的时候就拿出相应的VAO，绑定它，绘制完物体后，再解绑VAO。
    while (!glfwWindowShouldClose(window))
    {
        processInput1(window);
        //设置清除颜色
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        //使用上面设置的颜色清空屏幕
        // glClear(GL_COLOR_BUFFER_BIT);
        //因为使用了深度测试，我们也想要在每次渲染迭代之前清除深度缓冲（否则前一帧的深度信息仍然保存在缓冲中）。就像清除颜色缓冲一样
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);



        //调用glUseProgram函数，用刚创建的程序对象作为它的参数，以激活这个程序对象
        glUseProgram(objectShaderProgram);
        setObjectColorUniform(objectShaderProgram);
        //设置模型观察和投影矩阵
        viewMat(objectShaderProgram);
        projectionMat(objectShaderProgram);
        objectModelMat(objectShaderProgram);
        //绑定物体VAO
        glBindVertexArray(objectVAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        //调用glUseProgram函数，用刚创建的程序对象作为它的参数，以激活这个程序对象
        glUseProgram(lightShaderProgram);
        //设置模型，观察和投影矩阵
        viewMat(lightShaderProgram);
        projectionMat(lightShaderProgram);
        lightModelMat(lightShaderProgram);

        //绑定光VAO
        glBindVertexArray(lightVAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);


        //交换颜色缓冲区
        glfwSwapBuffers(window);
        //检查事件
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &objectVAO);
    glDeleteBuffers(1, &VBO);
    glDeleteVertexArrays(1, &lightVAO);
    //glfwTerminate会销毁窗口释放资源，因此在调用该函数后，如果想使用glfw库函数，就必须重新初始化。
    glfwTerminate();
    free(camera);
    return 0;
}

//鼠标回调
static void mouse_callback(GLFWwindow* window, double x, double y){
    //改变摄像机的方向
    mouseDirection(camera, window, x, y);
}
//滚轮回调
static void scroll_callback(GLFWwindow* window, double x_offset, double y_offset)
{
    //改变视角
    scrollFov(camera, window, x_offset, y_offset);
}